
namespace Nutils
{
    public static class XML
    {
        public class Writer : System.Xml.XmlTextWriter { public Writer(string FilePath) : base(FilePath, System.Text.Encoding.Unicode) { } }

        private static XML.Writer OpenWriter(string XmlFilePath)
        {
            try
            {
                return new XML.Writer(XmlFilePath);
            }
            catch (System.Exception ex)
            {
                Nutils.Console.Error("Failed to open '" + XmlFilePath + "' for writing.");
                Nutils.Console.Error(ex.Message);
                return null;
            }
        }
             
        public static XML.Writer OpenWriter_WaitTillUnlocked(string XmlFilePath)
        {
//            Hack.CheckFileLock(XmlFilePath);
            return XML.OpenWriter(XmlFilePath);
        }

        ///
        /// Tree 
        ///     
        public class Tree
        {
            public string name;
            public string text;
            public System.Collections.Generic.List<Tree> children;

            public Tree()
            {
                name = "";
                text = "";
                children = new System.Collections.Generic.List<Tree>();
            }

            /// <summary>
            /// Wrapper for iterator functionality
            /// </summary>
            public class Iter
            {
                readonly string searchPattern;
                private System.Collections.Generic.List<Tree> _matches;
                public System.Collections.Generic.List<Tree> matches { get { return _matches; } }

                public Iter(string searchPattern, Tree root)
                {
                    this.searchPattern = searchPattern;
                    _matches = new System.Collections.Generic.List<Tree>();
                    _Find(root);
                }

                private void _Find(Tree node)
                {
                    if (searchPattern == node.name)
                    {
                        _matches.Add(node);
                        return;
                    }

                    foreach (Tree child in node.children)
                    {
                        if (searchPattern == child.name)
                        {
                            _matches.Add(child);
                            continue;
                        }

                        _Find(child);
                    }
                }

                public System.Collections.Generic.IEnumerator<Tree> GetEnumerator()
                {
                    foreach (Tree match in matches)
                    {
                        yield return match;
                    }
                }
            }

            public Iter ForEach(string match)
            {
                return new XML.Tree.Iter(match, this);
            }
            
            // Collects all instances with the given name and returns
            // the 'text' for the last one it found. If one isn't found
            // returns 'defaultText' instead.
            public string FindTextValue(string match, string defaultText)
            {
                XML.Tree.Iter iter = new XML.Tree.Iter(match, this);
                if (iter.matches.Count > 0)
                {
                    return iter.matches[iter.matches.Count - 1].text;
                }

                return defaultText;
            }    
        }

        public static Tree Parse(string XmlFilePath)
        {
            try
            {
                using (System.Xml.XmlTextReader reader = new System.Xml.XmlTextReader(XmlFilePath))
                {
                    return Parse(reader);
                }
            }
            catch (System.Exception ex)
            {
                Nutils.Console.Error("Failed to open '" + XmlFilePath + "' for reading.");
                Nutils.Console.Error(ex.Message);
                return null;
            }
        }

        public static Tree Parse(System.IO.StringReader reader)
        {
            System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(reader);
            return Parse(xmlReader);
        }

        public static Tree Parse(System.Xml.XmlReader reader)
        {
            Tree root = new Tree();
            _Parse(reader, root);
            return root;
        }

        private static void _Parse(System.Xml.XmlReader reader, Tree node)
        {
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    // Summary:
                    //     This is returned by the System.Xml.XmlReader if a 
                    //     Read method has not been called.
                    case System.Xml.XmlNodeType.None:
                        break;
                    //
                    // Summary:
                    //     An element (for example, <item> ).
                    case System.Xml.XmlNodeType.Element:
                        if (0 >= node.name.Length)
                        {
                            //expanding self
                            node.name = reader.Name;
                            break;
                        }

                        // already expanded myself, recurse for child
                        Tree child = new Tree();
                        child.name = reader.Name;
                        node.children.Add(child);

                        // skip recurse if this is an empty element
                        // example, <item />
                        if (reader.IsEmptyElement)
                        {
                            break;
                        }

                        _Parse(reader, child);
                        break;
                    //
                    // Summary:
                    //     An attribute (for example, id='123' ).
                    case System.Xml.XmlNodeType.Attribute:
                        break;
                    //
                    // Summary:
                    //     The text content of a node.
                    case System.Xml.XmlNodeType.Text:
                        node.text = reader.Value;
                        break;
                    //
                    // Summary:
                    //     A CDATA section (for example, <![CDATA[my escaped text]]> ).
                    case System.Xml.XmlNodeType.CDATA:
                        break;
                    //
                    // Summary:
                    //     A reference to an entity (for example, &num; ).
                    case System.Xml.XmlNodeType.EntityReference:
                        break;
                    //
                    // Summary:
                    //     An entity declaration (for example, <!ENTITY...> ).
                    case System.Xml.XmlNodeType.Entity:
                        break;
                    //
                    // Summary:
                    //     A processing instruction (for example, <?pi test?> ).
                    case System.Xml.XmlNodeType.ProcessingInstruction:
                        break;
                    //
                    // Summary:
                    //     A comment (for example, <!-- my comment --> ).
                    case System.Xml.XmlNodeType.Comment:
                        break;
                    //
                    // Summary:
                    //     A document object that, as the root of the document tree, 
                    //     provides access to the entire XML document.
                    case System.Xml.XmlNodeType.Document:
                        break;
                    //
                    // Summary:
                    //     The document type declaration, indicated by the following tag
                    //     (for example, <!DOCTYPE...> ).
                    case System.Xml.XmlNodeType.DocumentType:
                        break;
                    //
                    // Summary:
                    //     A document fragment.
                    case System.Xml.XmlNodeType.DocumentFragment:
                        break;
                    //
                    // Summary:
                    //     A notation in the document type declaration
                    //     (for example, <!NOTATION...>).
                    case System.Xml.XmlNodeType.Notation:
                        break;
                    //
                    // Summary:
                    //     White space between markup.
                    case System.Xml.XmlNodeType.Whitespace:
                        break;
                    //
                    // Summary:
                    //     White space between markup in a mixed content model or white
                    //     space within the xml:space="preserve" scope.
                    case System.Xml.XmlNodeType.SignificantWhitespace:
                        break;
                    //
                    // Summary:
                    //     An end element tag (for example, </item> ).
                    case System.Xml.XmlNodeType.EndElement:
                        // we're done, time to leave
                        return;
                    //
                    // Summary:
                    //     Returned when XmlReader gets to the end of the entity 
                    //     replacement as a result of a call to 
                    //     System.Xml.XmlReader.ResolveEntity().
                    case System.Xml.XmlNodeType.EndEntity:
                        break;
                    //
                    // Summary:
                    //     The XML declaration (for example, <?xml version='1.0'?> ).
                    case System.Xml.XmlNodeType.XmlDeclaration:
                        break;
                }
            }

            return;
        }
    }
}
